第一作者:夏臻明 第二作者:陆远
有一天我走在路上,看到了若干位环卫工人顶着烈日为树木花草浇水。我不禁想要做些什么来减少环卫工人的负担。于是我们通过python语言,先用M5StickV采集样本,并进行神经网络训练使其能较为精确地识别树木种类。再在Arduino上编写程序,使其能够在实验中沿着已经贴有黑色胶布的既定路线循迹行进。另外,Arduino上的一部分代码也控制舵机,使其能够在树木种类被识别出来的时候调整成相对的合适的角度。最后组装成了一个智能浇水小车,它可以自主对路径检测,通过摄像头对不同的植物进行识别,并将识别结果发送给小车上Arduino,小车根据识别结果,做出相应的反应,对路边不同的植物进行识别以确定不同的浇水量,然后启动浇水装置,对其浇水,并通过LCD1602对结果进行显示。
关键词:神经网络,Arduino,循迹
引言
在生活中,路上我们能够见到的洒水车一般都是环卫工人驾驶的,且浇水的量没有控制。稍微好一点的浇水车可以人工对浇水量进行人工控制。这些都十分的不智能,在浪费时间的同时也浪费了大量的水,这对我们的水资源使用是不利的。较好的解决方法是针对不同的植物自动给予不同量的水。
近年来,随着大数据和AI的迅速发展,神经网络的研究和发展也在不断地深入。其应用领域可涉及至人脸识别、手写数字识别、图像分类等。
因此我们想到使用基于神经网络的模型,可以识别出不同的植物种类,并通过舵机的控制来给予不同量的水。同时为浇水车加上循迹功能(通过循迹模块以及预设轨迹实现)。
制作部分
2.1,实验材料
2.2,程序实现
(1)主程序伪代码
loop
begin(程序开始)
8*8点阵显示
byte rev
if Arduino串口接收到数据
begin
rev = 获取串口接收到的数据
串口监视器输出
判断树的种类
end
根据判断结果,LCD显示、舵机转动
循迹
end(程序结束)
(2)实现步骤
1、神经网络实现植物分类
(a)整体思路及原理
使用M5sitckV的训练程序获取3种树的训练模型;修改获得的模型代码,添加串口输出功能。
(b)连线
M5StickV接上GROVE 4Pin连接线即可。
(c)程序实现
from machine import UART # 导入库
fm.register(board_info.CONNEXT_B,fm.fpioa.UART1_TX)
fm.register(board_info.CONNEXT_A,fm.fpioa.UART1_RX)
# 定义uart,并初始化。波特率:9600;资料位:8;奇偶校验位:None;停止位:1;
uart = UART(UART.UART1, 9600, 8, None, 1, timeout=1000, read_buf_len=4096)
if pmax > 0.95: # 当识别的准确率大于0.95时
lcd.draw_string(40, 60, "Accu:%.2f Type:%s"%(pmax, labels[max_index].strip()))
uart.write(labels[max_index].strip()) # 串口数据发送
(d)串口调试
下图是USB转TTL模块的接线,接好线之后插到电脑即可(下图的模块主控芯片为CP2102,安装驱动后即可生成虚拟串口)。
连接完毕后,打开下载的串口调试助手软件,选择USB转TTL模块对应的串口号,设置好接收数据的波特率、校验位、数据位以及停止位,最后打开串口,如果可以接受到数据,右侧就会显示。
2、Arduino控制舵机实现水量多级控制
(a)舵机
舵机(如上图所示)是一种位置伺服的驱动器,主要是由外壳、路板、无核心马达、齿轮与位置检测器所构成。其工作原理是由接收机或者单片机发出信号给舵机,其内部有一个基准电路,产生周期为 20ms,宽度为1.5ms的基准信号,将获得的直流偏置电压与电位器的电压比较,获得电压差输出。经由电路板上的 IC 判断转动方向,再驱动无核心马达开始转动,透过减速齿轮将动力传至摆臂,同时由位置检测器送回信号,判断是否已经到达定位。适用于那些需要角度不断变化并可以保持的控制系统。
我们通过控制舵机所旋转的角度,来控制浇水组件的浇水量。舵机由Arduino控制,M5进行识别后将数据传输至Arduino,Arduino控制舵机旋转不同角度,实现舵机对水量的多级控制。
舵机都有外接三根线(如下图所示),分别用棕、红、橙三种颜色进行区分,由于舵机品牌不同,颜色也会有所差异,棕色为接地线,红色为电源正极线,橙色为信号线。
(b)连线
舵机的橙色线→黄色D2
舵机的红色线→任一红色引脚
舵机的棕色线→任一黑色引脚
(c)程序实现
#include 
Servo myservo; // 声明舵机类的变量
int servopin=2; // 设置舵机驱动脚到数字口 2
void setup(){
//绑定舵机引脚
myservo.attach(servopin);
}
void loop(){
myservo.write(0);
delay(1000);
myservo.write(45);
delay(1000);
myservo.write(90);
delay(1000);
}
(c)、Arduino串口接收以及LCD1602显示
以常见的Arduino UNO为例,面板上只有一组串行端口,即引脚 0(RX)和1(TX)。 电脑与Arduino的通信即通过这两个端口进行(即我们烧录程序就用的这个口),USB口通过一个转换芯片(通常为ATmega16 u2)与这两个串口引脚连接,虽然表面上电脑没有直接用外置的电线与这两个引脚相连,但是二者之间的效果是一样的。当Arduino控制器使用USB线与计算机相连时,两者之间便建立了串口连接。通过此连接,Arduino控制器可与计算机相互传数据了。
LCD 1602
我们使用IIC LCD1602模块集成了IIC I/O扩展芯片PCA8574(IIC和UART一样,也是硬件通信协议),使LCD 1602的使用更为简单。通过两线制的IIC总线(串行时钟线SCL,串行数据线SDA),可使Arduino实现控制LCD 1602显示的目的。既简化了电路,又节省了I/O口,使Arduino能实现更多的功能。通过模块上的电位器还可以调节LCD显示器的对比度。通过设置跳线还可以设置地址: 0x20-0x27。使Arduino能控制多块LCD 1602。模块背面可以看到一块银白色的电位器,旋转它可以调节1602液晶显示器的对比度。背后的接线引脚分别为GND;VCC;SDA;SCL(SDA和SCL分别为IIC通讯的数据线和时钟线)。
(a)程序实现
#include 
#include 
#include 
SoftwareSerial mySerial(12, 13); // 创建串口对象,定义管脚12、13分别为RX,TX
LiquidCrystal_I2C mylcd(0x3F,16,2);
int tree_class; // 声明tree_class,保存判断的到的树的种类
void setup(){
Serial.begin(9600);
mySerial.begin(9600); //串口工作前的配置,设置波特率为9600,默认8个数据位,无校验位,1个停止位
// LCD 1602初始化
mylcd.init();
mylcd.backlight();
mylcd.clear();
}
void loop(){
byte rev;
//LCD显示
mylcd.clear();
mylcd.setCursor(0, 0);
// 如果串口接收到数据
if (mySerial.available()){
rev = mySerial.read(); // 获取串口接收到的数据
Serial.println(rev); // 串口监视器显示
tree_class = rev-48; //将接收到的字符转换为数字
mylcd.print(tree_class );
}
(b)连线
在Arduino和M5StickV都关闭的情况下连接串口线。红线、黑线一定不要插在同样颜色的引脚上!黄线为接收数据线,用不到,不需要要连接。
GROVE 4Pin连接线白线→黄色D12;
GROVE 4Pin连接线红线→任一红色引脚;
GROVE 4Pin连接线黑线→任一黑色引脚。
(c)串口监视器查看
连接好串口线后,打开M5StickV,然后给Arduino上电。程序烧录完成后,打开Arduino的串口监视器,就可以看到接收到的M5StickV串口发送的信息了。
3、智能浇水车对循线功能的实现
(a)整体思路
循迹模块
利用红外线的物理性质来进行测量的传感器。红外线又称红外光,它具有反射、折射、散射、干涉、吸收等性质。任何物质,只要它本身具有一定的温度(高于绝对零度),都能辐射红外线。红外线传感器测量时不与被测物体直接接触,因而不存在摩擦,并且有灵敏度高,反应快等优点。
红外探测法红外探测法,即利用红外线在不同颜色的物体表面具有不同的反射强度的特点,在小车行驶过程中不断地向地面发射红外光,当红外光遇到白色纸质地板时发生漫反射,反射光被装在小车上的接收管接收;如果遇到黑线则红外光被吸收,小车的接收管接收不到红外光。单片机就是否收到反射回来的红外光为依据来确定黑线的位置。
我们在智能浇水车中配备了3个红外循迹传感器模块,在 Arduino 扩展板上对应着有红外循迹模块的接口,且是数字模块,即遇到黑色返回值为 0 、遇到白色返回值为 1。如果左循迹模块检测到黑线,说明小车偏右行驶,那么驱动小车左转;如果右循迹模块检测到白线,说明小车偏左行驶,那么驱动小车右转。
实际操作中,我们使用黑色定位胶带作为预设路线。
(a)连线
左循迹模块S→黄色D8
中循迹模块S→黄色D9
右循迹模块S→黄色D10
循迹模块V→任一红色引脚
循迹模块G→任一黑色引脚
电机驱动板IN1→黄色D5
电机驱动板IN2→黄色D6
电机驱动板IN3→黄色D11
电机驱动板IN4→黄色D3
(b)程序实现
// 循迹小车的代码要根据场地和传感器安装位置的不同仔细来调
int IN1=5; // 左电机后退(IN1)
int IN2=6; // 左电机前进(IN2)
int IN3=11; // 右电机前进(IN3)
int IN4=3; // 右电机后退(IN4)
int LIN=8;//左侧循迹模块
int MIN=9;//中间循迹模块
int RIN=10;//右侧循迹模块
void setup(){
//初始化电机驱动IO为输出方式
pinMode(IN1,OUTPUT);
pinMode(IN2,OUTPUT);
pinMode(IN3,OUTPUT);
pinMode(IN4,OUTPUT);
//初始化循迹引脚为输入状态
pinMode(LIN,INPUT_PULLUP);
pinMode(RIN,INPUT_PULLUP);
pinMode(MIN,INPUT_PULLUP);
}
// 前进
void forward(){
analogWrite(IN1,0);
analogWrite(IN2,90);//左电机前进PWM设置为200
analogWrite(IN3,90);//右电机前进PWM设置为200
analogWrite(IN4,0);
}
// 右转单轮
void turnleft(){
analogWrite(IN1,0);
analogWrite(IN2,0);
analogWrite(IN3,100);
analogWrite(IN4,0);
}
// 左转单轮
void turnright() {
analogWrite(IN1,0);
analogWrite(IN2,100);
analogWrite(IN3,0);
analogWrite(IN4,0);
}
// 停止
void stop(){
analogWrite(IN1,0);
analogWrite(IN2,0);
analogWrite(IN3,0);
analogWrite(IN4,0);
}
// 后退
void back(){
analogWrite(IN1,90);
analogWrite(IN2,0);
analogWrite(IN3,0);
analogWrite(IN4,90);
}
// 循迹
void tracking(){
int NLIN=digitalRead(LIN);
int NRIN=digitalRead(RIN);
int NMIN=digitalRead(MIN);
// 先读取三个循迹模块的值
// 左边模块检测到黑线则左拐
if(NLIN==0){
turnleft();
delay(50); // 等待50ms让小车完成转弯
}
// 中间模块检测到黑线则直行
else if(NMIN==0){
forward();
delay(50);
}
// 右边模块检测到黑线则右转
else if(NRIN==0){
turnright();
delay(50);
}
}
void loop(){
tracking();
}
4、完整程序
(a)M5StickV部分:
import image
import lcd
import sensor
import sys
import time
import KPU as kpu
from fpioa_manager import *
import KPU as kpu
from machine import UART
fm.register(board_info.CONNEXT_B,fm.fpioa.UART1_TX)
fm.register(board_info.CONNEXT_A,fm.fpioa.UART1_RX)
uart = UART(UART.UART1, 9600, 8, None, 1, timeout=1000, read_buf_len=4096)
lcd.init()
lcd.rotation(2)
try:
from pmu import axp192
pmu = axp192()
pmu.enablePMICSleepMode(True)
except:
pass
try:
img = image.Image("/sd/startup.jpg")
lcd.display(img)
except:
lcd.draw_string(lcd.width()//2-100,lcd.height()//2-4, "Error: Cannot find start.jpg", lcd.WHITE, lcd.RED)
task = kpu.load("/sd/fbb20e07ea604040_mbnet10_quant.kmodel")
labels=["1","2","3"]
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((224, 224))
sensor.run(1)
lcd.clear()
while(True):
img = sensor.snapshot()
fmap = kpu.forward(task, img)
plist=fmap[:]
pmax=max(plist)
max_index=plist.index(pmax)
a = lcd.display(img)
lcd.draw_string(40, 60, "Accu:%.2f Type:%s"%(pmax, labels[max_index].strip()))
if pmax > 0.95:
lcd.draw_string(40, 60, "Accu:%.2f Type:%s"%(pmax, labels[max_index].strip()))
uart.write(labels[max_index].strip())
a = kpu.deinit(task)
(b)Arduino部分:
#include 
#include 
#include 
#include 
int DIN = 3;
int CS = 6;
int CLK = 7;
byte stone[8]= {0x00,0x00,0x3C,0x3C,0x3C,0x3C,0x00,0x00}; //石头
byte scissors[8]= {0x81,0x42,0x24,0x18,0x18,0xE7,0xA5,0xE7}; //剪刀
byte pack[8]= {0xFF,0x81,0x81,0x81,0x81,0x81,0x81,0xFF}; //布
LiquidCrystal_I2C mylcd(0x27,16,2);
SoftwareSerial mySerial(10, 11); // RX, TX
LedControl lc=LedControl(DIN,CLK,CS,4);
int mine, yours;
void setup()
{
Serial.begin(9600);
mySerial.begin(9600);
mylcd.init();
mylcd.backlight();
mylcd.clear();
mine=1;
yours=1;
lc.shutdown(0,false); //启动时,MAX72XX处于省电模式
lc.setIntensity(0,8); //将亮度设置为最大值
lc.clearDisplay(0); //清除显示
}
void compete(int mine, int yours){
mylcd.clear();
mylcd.setCursor(7, 0);
mylcd.print("VS");
if(mine==1){
mylcd.setCursor(1, 0);
mylcd.print("stone");
}
else if(mine==2){
mylcd.setCursor(2, 0);
mylcd.print("cut");
}
else{
mylcd.setCursor(1, 0);
mylcd.print("pack");
}
if(yours==1){
mylcd.setCursor(10, 0);
mylcd.print("stone");
}
else if(yours==2){
mylcd.setCursor(11, 0);
mylcd.print("cut");
}
else{
mylcd.setCursor(11, 0);
mylcd.print("pack");
}
if(yours - mine == 1|| mine-yours ==2){
mylcd.setCursor(0, 1);
mylcd.print(" I win!");
}
else if(mine - yours == 1|| yours-mine ==2){
mylcd.setCursor(0, 1);
mylcd.print(" You win!");
}
else if(mine==yours){
mylcd.setCursor(0, 1);
mylcd.print(" Same!");
}
else {
mylcd.setCursor(0, 1);
mylcd.print(" Pass");
}
}
//点阵显示函数
void printByte(byte character [])
{
int i = 0;
for(i=0;i<8;i++)
{
lc.setRow(0,i,character[i]);
}
}
void loop()
{
byte rev;
if (mySerial.available())
{
rev = mySerial.read();
Serial.println(rev);
yours = rev-48;
}
//Serial.println(digitalRead(8));
if (digitalRead(8) == HIGH) {
while (digitalRead(8) == HIGH) {
delay(10);
}
mine = random(1, 4);
if(mine==1){
printByte(stone);
}
else if(mine==2){
printByte(scissors);
}
else{
printByte(pack);
}
}
compete( mine, yours);
}
6、装配
7、测试
我们使用黑色定位胶带模拟道路,使用了3中不同的模型树作为训练集。训练M5模型并将其烧录入M5StickV中。启动小车,小车沿着定位胶带进行自动循迹,同时针对不同的模型树给予不同水量。
我们发现对于识别不同种类的树并给予相对应的量的水时基本没有错误发生,但是循迹的过程有时会发生偏离轨道的现象。
8、总结
综上所述,我们做了一个智能浇水小车。它可以自主对路径检测,通过红外循迹传感器以及Arduino来实现自动循迹功能,同时可以通过M5StickV对不同的植物进行识别,并将识别结果发送给小车上的Arduino,小车根据识别结果,对路边不同的植物确定不同的浇水量。然后启动浇水装置,通过Arduino对舵机的分级控制实现不同的浇水量,并通过LCD1602对识别结果进行显示。
这在日常生活中可以找到一定的应用场景,有一定的实用价值。
9、展望
我们需要改进传感器的性能及其反馈效率
充分发挥Arduino的蓝牙模块
开发对于可能障碍物的规避能力
10、参考文献
[1]高玉双.深度学习在计算机视觉领域的应用发展探究[J].电脑编程技巧与维护,2020(09):125-127.
[2]蒲俊福. 基于深度学习的视频手语识别研究[D].中国科学技术大学,2020.
[3]姜世奇. 基于计算机视觉的牛个体身份识别方法研究[D].内蒙古科技大学,2020.
[4]廖荣凡. 基于神经网络的视觉无人机安检系统设计[D].上海应用技术大学,2020.
[5]蔡江涛. 基于深度学习的带标题图像多标签分类[D].电子科技大学,2020.
[6]孙沐毅. 基于深度学习的数字病理图像识别分析与应用研究[D].北京邮电大学,2020.
[7]关捷雄. 基于计算机视觉的物体抓取识别算法的研究与实现[D].上海师范大学,2019.
[8]郭佳伟. 基于计算机视觉的驾驶员异常行为识别与预警[D].大连海事大学,2019.
[9]王雨. 面向自动驾驶场景的高效实时语义分割方法研究[D].南京邮电大学,2020.
[10]李紫玮. 基于深度学习理论的桥梁上车型识别与检测跟踪研究[D].湖南大学,2019.
[11]胡旭. 基于注意力机制的金丝猴面部识别研究与实现[D].西安电子科技大学,2019.
[12]万超伦. 基于神经网络的人体动作识别算法研究[D].江西理工大学,2019.
| 软件 | 软件 | 硬件 | 硬件 | 
| 类目 | 功能 | 类目 | 功能 | 
| maixpyide | 编写M5程序 | M5StickV | 拍照、识别 | 
| Arduino 软件 | 编写Arduino程序 | USB Type-C | 供电、烧录程序 | 
| GROVE 4Pin连接线 | UART串口线 | ||
| 9号智能小车 | 主控 | ||
| LCD 1602模块 | 识别结果显示 | ||
| 8*8点阵模块 | 浇水指示 | ||
| 母对母杜邦线 | 连接LCD 1602、 8*8点阵 | ||
| 公对杜邦线 | 连接串口线 |